//*****************************************************************************
//
// medit.c
//
// When mob prototypes became python scripts, OLC for mobs had to be rethought.
// Ideally, all builders would have a basic grasp on python and thus would be
// able to write scripts. Ideally. Sadly, I don't think this can be expected
// out of most builders, and we still need some sort of non-scripting interface
// for editing mobs. So here it is...
//
//*****************************************************************************
#include "mud.h"
#include "utils.h"
#include "socket.h"
#include "races.h"
#include "world.h"
#include "character.h"
#include "prototype.h"
#include "handler.h"
#include "room.h"

#include "olc.h"
#include "olc_extender.h"



//*****************************************************************************
// mandatory modules
//*****************************************************************************
#include "editor.h"
#include "scripts.h"
#include "script_editor.h"



//*****************************************************************************
// mob olc structure, and functions
//*****************************************************************************
typedef struct {
  char          *key; // the key for our prototype
  char      *parents; // things we inherit from
  bool      abstract; // can we be laoded into the game?
  CHAR_DATA      *ch; // our character, which holds most of our variables
  BUFFER *extra_code; // any extra code that should go to our prototype
} CHAR_OLC;

CHAR_OLC *newCharOLC(void) {
  CHAR_OLC *data  = malloc(sizeof(CHAR_OLC));
  data->key       = strdup("");
  data->parents   = strdup("");
  data->abstract  = TRUE;
  data->ch        = newMobile();
  charSetRace(data->ch, "");
  data->extra_code = newBuffer(1);
  charSetSex(data->ch, SEX_NONE);

  // so python olc extensions can get at us
  char_exist(data->ch);

  return data;
}

void deleteCharOLC(CHAR_OLC *data) {
  char_unexist(data->ch);

  if(data->key)        free(data->key);
  if(data->parents)    free(data->parents);
  if(data->extra_code) deleteBuffer(data->extra_code);
  if(data->ch)         deleteChar(data->ch);
  free(data);
}

const char *charOLCGetKey(CHAR_OLC *data) {
  return data->key;
}

const char *charOLCGetParents(CHAR_OLC *data) {
  return data->parents;
}

bool charOLCGetAbstract(CHAR_OLC *data) {
  return data->abstract;
}

CHAR_DATA *charOLCGetChar(CHAR_OLC *data) {
  return data->ch;
}

BUFFER *charOLCGetExtraCode(CHAR_OLC *data) {
  return data->extra_code;
}

void charOLCSetKey(CHAR_OLC *data, const char *key) {
  if(data->key) free(data->key);
  data->key = strdup(key);
}

void charOLCSetParents(CHAR_OLC *data, const char *parents) {
  if(data->parents) free(data->parents);
  data->parents = strdup(parents);
}

void charOLCSetAbstract(CHAR_OLC *data, bool abstract) {
  data->abstract = abstract;
}

//
// takes in a char prototype, and tries to generate a char olc out of it. This
// function is messy and ugly and icky and yuck. But, alas, I cannot think of
// a better way to do this. Maybe next version...
CHAR_OLC *charOLCFromProto(PROTO_DATA *proto) {
  CHAR_OLC *data = newCharOLC();
  CHAR_DATA  *ch = charOLCGetChar(data);
  charOLCSetKey(data, protoGetKey(proto));
  charOLCSetParents(data, protoGetParents(proto));
  charOLCSetAbstract(data, protoIsAbstract(proto));

  // build it from the prototype
  olc_from_proto(proto, charOLCGetExtraCode(data), ch, charGetPyFormBorrowed);
  bufferFormatFromPy(charGetDescBuffer(ch));
  bufferFormat(charGetDescBuffer(ch), SCREEN_WIDTH, PARA_INDENT);

  // do all of our extender data as well
  extenderFromProto(medit_extend, ch);

  return data;
}

//
// takes in a char olc and tries to generate a prototype out of it
PROTO_DATA *charOLCToProto(CHAR_OLC *data) {
  PROTO_DATA *proto = newProto();
  CHAR_DATA     *ch = charOLCGetChar(data);
  BUFFER       *buf = protoGetScriptBuffer(proto);
  protoSetKey(proto, charOLCGetKey(data));
  protoSetParents(proto, charOLCGetParents(data));
  protoSetAbstract(proto, charOLCGetAbstract(data));

  bprintf(buf, "### The following mproto was generated by medit\n");
  bprintf(buf, "### If you edit this script, adhere to the stylistic\n"
	       "### conventions laid out by medit, or delete the top line\n");

  bprintf(buf, "\n### keywords, short descs, room descs, and look descs\n");
  if(*charGetKeywords(ch))
    bprintf(buf, "me.keywords = ', '.join([me.keywords, \"%s\"])\n",
	    charGetKeywords(ch));
  if(*charGetName(ch))
    bprintf(buf, "me.name     = \"%s\"\n", charGetName(ch));
  if(*charGetMultiName(ch))
    bprintf(buf, "me.mname    = \"%s\"\n", charGetMultiName(ch));
  if(*charGetRdesc(ch))
    bprintf(buf, "me.rdesc    = \"%s\"\n", charGetRdesc(ch));
  if(*charGetMultiRdesc(ch))
    bprintf(buf, "me.mdesc    = \"%s\"\n", charGetMultiRdesc(ch));
  if(*charGetDesc(ch)) {
    BUFFER *desc_copy = bufferCopy(charGetDescBuffer(ch));
    bufferFormatPy(desc_copy);
    bprintf(buf, "me.desc     = me.desc + ' ' + \"%s\"\n", 
	    bufferString(desc_copy));
    deleteBuffer(desc_copy);
  }
  
  if(*charGetRace(ch) || charGetSex(ch) != SEX_NONE) {
    bprintf(buf, "\n### race and gender\n");
    if(*charGetRace(ch))
      bprintf(buf, "me.race     = \"%s\"\n", charGetRace(ch));
    if(charGetSex(ch) != SEX_NONE)
      bprintf(buf, "me.gender   = \"%s\"\n", sexGetName(charGetSex(ch)));
  }

  if(listSize(charGetTriggers(ch)) > 0) {
    bprintf(buf, "\n### character triggers\n");
    LIST_ITERATOR *trig_i = newListIterator(charGetTriggers(ch));
    char            *trig = NULL;
    ITERATE_LIST(trig, trig_i) {
      bprintf(buf, "me.attach(\"%s\")\n",get_shortkey(trig,protoGetKey(proto)));
    } deleteListIterator(trig_i);
  }

  // print all of our extender data as well
  extenderToProto(medit_extend, ch, buf);

  if(bufferLength(charOLCGetExtraCode(data)) > 0) {
    bprintf(buf, "\n### begin extra code\n");
    bprintf(buf, "%s", bufferString(charOLCGetExtraCode(data)));
    bprintf(buf, "### end extra code\n");
  }
  return proto;
}



//*****************************************************************************
// mobile editing
//*****************************************************************************
#define MEDIT_PARENTS       1
#define MEDIT_NAME          2
#define MEDIT_MULTI_NAME    3
#define MEDIT_KEYWORDS      4
#define MEDIT_RDESC         5
#define MEDIT_MULTI_RDESC   6
#define MEDIT_RACE          7
#define MEDIT_SEX           8


void medit_menu(SOCKET_DATA *sock, CHAR_OLC *data) {
  send_to_socket(sock,
		 "{g[{c%s{g]\r\n"
		 "{g1) Abstract: {c%s\r\n"
		 "{g2) Inherits from prototypes:\r\n"
		 "{c%s\r\n"
		 "{g3) Name\r\n"
		 "{c%s\r\n"
		 "{g4) Name for multiple occurences:\r\n"
		 "{c%s\r\n"
		 "{g5) Keywords:\r\n"
		 "{c%s\r\n"
		 "{g6) Room description:\r\n"
		 "{c%s\r\n"
		 "{g7) Room description for multiple occurences:\r\n"
		 "{c%s\r\n"
		 "{g8) Description:\r\n"
		 "{c%s"
		 "{gT) Trigger menu\r\n"
		 "{gR) Change race   {y[{c%8s{y]\r\n"
		 "{gG) Change Gender {y[{c%8s{y]\r\n",
		 charOLCGetKey(data),
		 (charOLCGetAbstract(data) ? "yes" : "no"),
		 charOLCGetParents(data),
		 charGetName(charOLCGetChar(data)),
		 charGetMultiName(charOLCGetChar(data)),
		 charGetKeywords(charOLCGetChar(data)),
		 charGetRdesc(charOLCGetChar(data)),
		 charGetMultiRdesc(charOLCGetChar(data)),
		 charGetDesc(charOLCGetChar(data)),
		 (!*charGetRace(charOLCGetChar(data)) ?
		  "leave unchanged" :
		  charGetRace(charOLCGetChar(data))),
		 (charGetSex(charOLCGetChar(data)) == SEX_NONE ? 
		  "leave unchanged" :
		  sexGetName(charGetSex(charOLCGetChar(data))))
		 );

  // display our extender menu options
  extenderDoMenu(sock, medit_extend, charOLCGetChar(data));

  // only allow code editing for people with scripting priviledges
  send_to_socket(sock, "{gC) Extra code%s\r\n", 
		 ((!socketGetChar(sock) ||  
		   !bitIsOneSet(charGetUserGroups(socketGetChar(sock)),
				"scripter")) ? "    {y({cuneditable{y){g":""));
  script_display(sock, bufferString(charOLCGetExtraCode(data)), FALSE);
}

int  medit_chooser(SOCKET_DATA *sock, CHAR_OLC *data, const char *option) {
  switch(toupper(*option)) {
  case '1':
    charOLCSetAbstract(data, (charOLCGetAbstract(data) + 1) % 2);
    return MENU_NOCHOICE;
  case '2':
    text_to_buffer(sock,"Enter comma-separated list of mobs to inherit from: ");
    return MEDIT_PARENTS;
  case '3':
    text_to_buffer(sock, "Enter name: ");
    return MEDIT_NAME;
  case '4':
    text_to_buffer(sock, "Enter name for multiple occurences: ");
    return MEDIT_MULTI_NAME;
  case '5':
    text_to_buffer(sock, "Enter keywords: ");
    return MEDIT_KEYWORDS;
  case '6':
    text_to_buffer(sock, "Enter room description: ");
    return MEDIT_RDESC;
  case '7':
    text_to_buffer(sock, "Enter room description for multiple occurences: ");
    return MEDIT_MULTI_RDESC;
  case '8':
    text_to_buffer(sock, "Enter description\r\n");
    socketStartEditor(sock,text_editor,charGetDescBuffer(charOLCGetChar(data)));
    return MENU_NOCHOICE;
  case 'R':
    send_to_socket(sock, "%s\r\n\r\n", raceGetList(FALSE));
    text_to_buffer(sock, "Please select a race: ");
    return MEDIT_RACE;
  case 'G':
    olc_display_table(sock, sexGetName, NUM_SEXES, 1);
    text_to_buffer(sock, "Pick a gender: ");
    return MEDIT_SEX;
  case 'T':
    do_olc(sock, trigger_list_menu, trigger_list_chooser, trigger_list_parser,
	   NULL, NULL, NULL, NULL, charGetTriggers(charOLCGetChar(data)));
    return MENU_NOCHOICE;
  case 'C':
    // only scripters can edit extra code
    if(!socketGetChar(sock) || 
       !bitIsOneSet(charGetUserGroups(socketGetChar(sock)), "scripter"))
      return MENU_CHOICE_INVALID;
    text_to_buffer(sock, "Edit extra code\r\n");
    socketStartEditor(sock,script_editor,charOLCGetExtraCode(data));
    return MENU_NOCHOICE;
  default: 
    return extenderDoOptChoice(sock,medit_extend,charOLCGetChar(data),*option);
  }
}

bool medit_parser(SOCKET_DATA *sock, CHAR_OLC *data, int choice, 
		      const char *arg) {
  switch(choice) {
  case MEDIT_PARENTS:
    charOLCSetParents(data, arg);
    return TRUE;
  case MEDIT_NAME:
    charSetName(charOLCGetChar(data), arg);
    return TRUE;
  case MEDIT_MULTI_NAME:
    charSetMultiName(charOLCGetChar(data), arg);
    return TRUE;
  case MEDIT_KEYWORDS:
    charSetKeywords(charOLCGetChar(data), arg);
    return TRUE;
  case MEDIT_RDESC:
    charSetRdesc(charOLCGetChar(data), arg);
    return TRUE;
  case MEDIT_MULTI_RDESC:
    charSetMultiRdesc(charOLCGetChar(data), arg);
    return TRUE;
  case MEDIT_RACE:
    if(!isRace(arg))
      return FALSE;
    charSetRace(charOLCGetChar(data), arg);
    return TRUE;
  case MEDIT_SEX: {
    int val = atoi(arg);
    if(!isdigit(*arg) || val < 0 || val >= NUM_SEXES)
      return FALSE;
    charSetSex(charOLCGetChar(data), val);
    return TRUE;
  }
  default: 
    return extenderDoParse(sock,medit_extend,charOLCGetChar(data),choice,arg);
  }
}

void save_mob_olc(CHAR_OLC *data) {
  PROTO_DATA *old_proto = worldGetType(gameworld, "mproto",charOLCGetKey(data));
  PROTO_DATA *new_proto = charOLCToProto(data);
  if(old_proto == NULL)
    worldPutType(gameworld, "mproto", protoGetKey(new_proto), new_proto);
  else {
    protoCopyTo(new_proto, old_proto);
    deleteProto(new_proto);
  }

  worldSaveType(gameworld, "mproto", charOLCGetKey(data));
}



//*****************************************************************************
// commands
//*****************************************************************************
COMMAND(cmd_medit) {
  ZONE_DATA    *zone = NULL;
  PROTO_DATA  *proto = NULL;

  // we need a key
  if(!arg || !*arg)
    send_to_char(ch, "What is the name of the mob you want to edit?\r\n");
  else if(key_malformed(arg))
    send_to_char(ch, "You entered an invalid content key.\r\n");
  else {
    char locale[SMALL_BUFFER];
    char   name[SMALL_BUFFER];
    if(!parse_worldkey_relative(ch, arg, name, locale))
      send_to_char(ch, "Which mob are you trying to edit?\r\n");
    // make sure we can edit the zone
    else if((zone = worldGetZone(gameworld, locale)) == NULL)
      send_to_char(ch, "No such zone exists.\r\n");
    else if(!canEditZone(zone, ch))
      send_to_char(ch, "You are not authorized to edit that zone.\r\n");
    else {
      // try to make our OLC datastructure
      CHAR_OLC *data = NULL;

      // try to pull up the prototype
      proto = worldGetType(gameworld, "mproto", get_fullkey(name, locale));

      // if we already have proto data, try to parse a char olc out of it
      if(proto != NULL) {
	// check to make sure the prototype was made by medit
	char line[SMALL_BUFFER];
	strcpyto(line, protoGetScript(proto), '\n');
	if(strcmp(line, "### The following mproto was generated by medit")!=0){
	  send_to_char(ch, "This mob was not generated by medit and potential "
		      "formatting problems prevent medit from being used. To "
		       "edit, mpedit must be used\r\n");
	  return;
	}
	else
	  data = charOLCFromProto(proto);
      }
      // otherwise, make a new char olc and assign its key
      else {
	data = newCharOLC();
	charOLCSetKey(data, get_fullkey(name, locale));
	charOLCSetAbstract(data, TRUE);

	CHAR_DATA *mob = charOLCGetChar(data);
	charSetName(mob,       "an unfinished mobile");
	charSetMultiName(mob,  "%d unfinished mobiles");
	charSetKeywords(mob,   "mobile, unfinished");
	charSetRdesc(mob,      "an unfinished mobile is standing here.");
	charSetMultiRdesc(mob, "A group of %d mobiles are here, "
			       "looking unfinished.");
	charSetDesc(mob,       "It looks unfinished.\r\n");
      }
      
      do_olc(charGetSocket(ch), medit_menu, medit_chooser, medit_parser, 
	     NULL, NULL, deleteCharOLC, save_mob_olc, data);
    }
  }
}
